package com.example.demo.service.exam.impl;

import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.example.demo.base.config.auth.UserInfoShareHolder;
import com.example.demo.base.login.dto.UserCache;
import com.example.demo.domain.CombineClause;
import com.example.demo.domain.PageResult;
import com.example.demo.domain.SingleClause;
import com.example.demo.domain.entity.User;
import com.example.demo.domain.exam.ExamPaper;
import com.example.demo.domain.exam.ExamPaperRelation;
import com.example.demo.domain.exam.QueryModel;
import com.example.demo.domain.exam.Sorting;
import com.example.demo.domain.exam.condition.ConditionParser;
import com.example.demo.domain.exam.condition.ExamPaperCondition;
import com.example.demo.dto.ObjectiveQuestionDTO;
import com.example.demo.dto.ProblemPointsDTO;
import com.example.demo.dto.ProblemViewDto;
import com.example.demo.dto.ProgrammingQuestionDTO;
import com.example.demo.dto.QuestionNumDTO;
import com.example.demo.enums.PaperType;
import com.example.demo.enums.QuestionType;
import com.example.demo.exception.ExceptionUtils;
import com.example.demo.mapper.CompanyMapper;
import com.example.demo.mapper.ExamPaperMapper;
import com.example.demo.service.VojObjectiveQuestionService;
import com.example.demo.service.impl.BaseServiceImpl;
import com.example.demo.service.NewProblemService;
import com.example.demo.service.VojObjectiveQuestionsService;
import com.example.demo.service.exam.ExamPaperRelationService;
import com.example.demo.service.exam.ExamPaperService;
import com.example.demo.utils.JsonUtils;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import java.math.BigDecimal;
import java.sql.Timestamp;
import java.util.*;
import java.util.stream.Collectors;

@Service
@RequiredArgsConstructor
public class ExamPaperServiceImpl extends BaseServiceImpl<ExamPaperMapper, ExamPaper> implements ExamPaperService {

    private final ExamPaperMapper mapper;

    private final ExamPaperRelationService examPaperRelationService;

    private final NewProblemService newProblemService;

    private final VojObjectiveQuestionsService ObjectiveQuestionsService;

    private final VojObjectiveQuestionService vojObjectiveQuestionService;

    private final CompanyMapper companyMapper;

    @Override
    public PageResult<ExamPaper> page(QueryModel<ExamPaperCondition> query) {
        UserCache userInfo = UserInfoShareHolder.getUserInfo();

        CombineClause clauses = CombineClause.and(
                ConditionParser.parser(query.getCondition()),
                SingleClause.equal("disabled", false),
                SingleClause.equal("creator_id", userInfo.getUser().getId())
        );
        Sorting[] sorts = query.getSorts();
        //创建一个新的 Sorting 数组，包含原有的排序规则和 id 倒序排序
        List<Sorting> sortingList = sorts == null ? new ArrayList<>() : new ArrayList<>(Arrays.asList(sorts));
        sortingList.add(Sorting.descending("create_time"));
        Sorting[] newSorts = sortingList.toArray(new Sorting[0]);
        query.setSorts(newSorts);
        return super.selectPage(clauses, query.getPaging(), query.getSorts());
    }

    @Override
    public List<ExamPaper> getAllExamPaperByCondition(QueryModel<ExamPaperCondition> model) {
        return super.selectList(model.getCondition(), model.getSorts());
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void createExamPaper(List<ExamPaper> examPapers) {
        if (CollectionUtils.isEmpty(examPapers)) {
            return;
        }
        examPapers.forEach(e -> {
            // TODO 设置创建试卷的培训师标识
            User user = UserInfoShareHolder.getUserInfo().getUser();
            Long id = user.getId();
            e.setCreatorId(id);
            e.setCompanyId(UserInfoShareHolder.getUserInfo().getCompanyId());
            e.setCreateTime(new Timestamp(System.currentTimeMillis()));
            this.valid(e);
            int num = Optional.ofNullable(e.getObjectiveQuestions()).orElse(Collections.emptyList()).size()
                    + Optional.ofNullable(e.getProgrammingQuestions()).orElse(Collections.emptyList()).size();
            e.setNum(num);
        });
        this.saveBatch(examPapers);
        // mybatis-plus给对象主键赋值,插入后可以获取到
        List<ExamPaperRelation> relations = new ArrayList<>();
        examPapers.forEach(e -> {
            List<ObjectiveQuestionDTO> objectiveQuestions = e.getObjectiveQuestions();
            List<ProgrammingQuestionDTO> programmingQuestions = e.getProgrammingQuestions();
            if (!CollectionUtils.isEmpty(objectiveQuestions)) {
                e.getObjectiveQuestions().forEach(question -> {
                    ExamPaperRelation examPaperRelation = new ExamPaperRelation();
                    examPaperRelation.setPaperId(e.getId());
                    examPaperRelation.setQuestionId(question.getId());
                    examPaperRelation.setQuestionType(QuestionType.OBJECTIVE_QUESTION);
                    examPaperRelation.setPercentage(question.getPercentage());
                    relations.add(examPaperRelation);
                });
            }
            if (!CollectionUtils.isEmpty(programmingQuestions)) {
                e.getProgrammingQuestions().forEach(question -> {
                    ExamPaperRelation examPaperRelation = new ExamPaperRelation();
                    examPaperRelation.setPaperId(e.getId());
                    examPaperRelation.setQuestionId(question.getProblemId());
                    examPaperRelation.setQuestionType(QuestionType.PROGRAMMING_QUESTION);
                    examPaperRelation.setPercentage(question.getPercentage());
                    relations.add(examPaperRelation);
                });
            }
        });
        System.out.println("relations"+relations);
        if (!CollectionUtils.isEmpty(relations)) {
            examPaperRelationService.insertList(relations);
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deleteExamPaper(Long id) {
        if (id == null) {
            throw ExceptionUtils.exception("被删除的试卷标识不能为空");
        }
        mapper.deleteById(id);
        examPaperRelationService.deleteByPaperId(id);
    }

    private void valid(ExamPaper examPaper) {
        String title = examPaper.getTitle();
        // TODO 校验参数是否为空，为空报错返回
        if (StringUtils.isBlank(title)) {
            throw ExceptionUtils.exception("试卷试卷标题不能为空");
        }
        CombineClause clauses = CombineClause.and(
                SingleClause.equal("company_id", examPaper.getCompanyId()),
                SingleClause.equal("title", title)
        );
        if (examPaper.getId() != null) {
            clauses.add(SingleClause.notEqual("id", examPaper.getId()));
        }
        boolean exists = super.exists(clauses);
        if (exists) {
            throw ExceptionUtils.exception("存在重名试卷名称：%s", title);
        }
        List<ObjectiveQuestionDTO> objectiveQuestions = examPaper.getObjectiveQuestions();
        List<ProgrammingQuestionDTO> programmingQuestions = examPaper.getProgrammingQuestions();
//        // 检查 objectiveQuestions 是否为 null 并将其转换为一个空列表
//        if (objectiveQuestions == null) {
//            objectiveQuestions = Collections.emptyList();
//        }
//
//        // 检查 programmingQuestions 是否为 null 并将其转换为一个空列表
//        if (programmingQuestions == null) {
//            programmingQuestions = Collections.emptyList();
//        }
        if (CollectionUtils.isEmpty(objectiveQuestions)
                && CollectionUtils.isEmpty(programmingQuestions)) {
            throw ExceptionUtils.exception("试卷题目不能为空");
        }
        List<Double> percentages = new ArrayList<>();
        objectiveQuestions.forEach(e -> percentages.add(e.getPercentage()));
        programmingQuestions.forEach(e -> percentages.add(e.getPercentage()));
        BigDecimal percentageSum = percentages.stream().map(BigDecimal::valueOf).reduce(BigDecimal.ZERO, BigDecimal::add);
        if (percentageSum.compareTo(new BigDecimal(1)) != 0) {
            throw ExceptionUtils.exception("所有题目总和需为100%");
        }
    }

    /**
     * 根据试卷ID和试卷类型获取试卷详情
     *
     * @param paperId 试卷ID，用于唯一标识试卷
     * @param paperType 试卷类型，确定返回的试卷细节类型（仅客观题、仅编程题或所有题型）
     * @return 返回试卷详情对象，包含试卷的基本信息和题目信息
     * @throws 如果paperId或paperType为空，或者试卷不存在，或者试卷类型不正确，抛出相应的异常
     */
    @Override
    public ExamPaper getExamPaperDetail(Long paperId, PaperType paperType) {
        // 校验试卷ID是否为空
        if (paperId == null) {
            throw ExceptionUtils.exception("试卷标识不能为空");
        }
        // 校验试卷类型是否为空
        if (paperType == null) {
            throw ExceptionUtils.exception("试卷类型不能为空");
        }
        // 通过ID获取试卷详情，如果不存在则抛出异常
        ExamPaper examPaper = Optional.ofNullable(mapper.selectById(paperId)).orElseThrow(() -> ExceptionUtils.exception("试卷不存在"));

        // 获取试卷关联的题目信息
        List<ExamPaperRelation> relationByPaperId = examPaperRelationService.getRelationByPaperId(paperId);

        // 收集题目ID和其对应的比例
        Map<Long, Double> map = relationByPaperId.stream()
                .collect(Collectors.toMap(ExamPaperRelation::getQuestionId, ExamPaperRelation::getPercentage));

        // 根据题目类型收集题目ID
        Map<QuestionType, List<Long>> collect = relationByPaperId.stream().collect(Collectors.groupingBy(ExamPaperRelation::getQuestionType
                , Collectors.mapping(ExamPaperRelation::getQuestionId, Collectors.toList())));

        // 定义客观题和编程题的变量
        List<ObjectiveQuestionDTO> objectiveQuestions;
        List<ProgrammingQuestionDTO> programmingQuestions;

        // 根据试卷类型处理题目信息
        switch (paperType) {
            case ONLY_OBJECTIVE_QUESTIONS:
                // 获取客观题列表并设置到试卷对象中
                objectiveQuestions = this.getObjectiveQuestions(collect.get(QuestionType.OBJECTIVE_QUESTION), map);
                examPaper.setObjectiveQuestions(objectiveQuestions);
                break;
            case ONLY_PROGRAMMING_QUESTIONS:
                // 获取编程题列表并设置到试卷对象中
                programmingQuestions = this.getProgrammingQuestions(collect.get(QuestionType.PROGRAMMING_QUESTION), map);
                examPaper.setProgrammingQuestions(programmingQuestions);
                break;
            case ALL_QUESTIONS:
                // 同时获取客观题和编程题列表并设置到试卷对象中
                objectiveQuestions = this.getObjectiveQuestions(collect.get(QuestionType.OBJECTIVE_QUESTION), map);
                programmingQuestions = this.getProgrammingQuestions(collect.get(QuestionType.PROGRAMMING_QUESTION), map);
                examPaper.setObjectiveQuestions(objectiveQuestions);
                examPaper.setProgrammingQuestions(programmingQuestions);
                break;
            default:
                // 如果试卷类型不正确，抛出异常
                throw ExceptionUtils.exception("试卷类型错误");
        }
        // 返回填充好的试卷详情对象
        return examPaper;
    }

    private List<ObjectiveQuestionDTO> getObjectiveQuestions(List<Long> ids, Map<Long, Double> map) {
        List<ObjectiveQuestionDTO> objectiveQuestions = JsonUtils.deserializeList(JsonUtils.serialize(vojObjectiveQuestionService.getByIds(ids)), ObjectiveQuestionDTO.class);
        for (ObjectiveQuestionDTO dto : objectiveQuestions) {
            Double percentage = map.get(dto.getId());
            dto.setPercentage(percentage);
        }
        return objectiveQuestions;
    }

    private List<ProgrammingQuestionDTO> getProgrammingQuestions(List<Long> ids, Map<Long, Double> map) {
        List<ProgrammingQuestionDTO> programmingQuestions = JsonUtils.deserializeList(JsonUtils.serialize(newProblemService.getProblemByIds(ids)), ProgrammingQuestionDTO.class);
        for (ProgrammingQuestionDTO dto : programmingQuestions) {
            Double percentage = map.get(dto.getProblemId());
            dto.setPercentage(percentage);
        }
        return programmingQuestions;
    }

    @Override
    public ExamPaper randomGroupPaper(QuestionNumDTO dto) {
        validGroupParams(dto);
        Integer objNum = dto.getObjectiveNum();
        Integer proNum = dto.getProgrammingNum();
        Double objPercentage = dto.getObjectivePercentage();
        Double proPercentage = dto.getProgrammingPercentage();
        ExamPaper examPaper = this.saveExamPaper(dto);
        List<ProgrammingQuestionDTO> programmingByRandom = newProblemService.getProgrammingByRandom(proNum);
        List<ObjectiveQuestionDTO> objectiveByRandom = ObjectiveQuestionsService.getObjectiveByRandom(objNum);
        examPaper.setObjectiveQuestions(objectiveByRandom);
        examPaper.setProgrammingQuestions(programmingByRandom);
        List<ExamPaperRelation> relations = new ArrayList<>();
        programmingByRandom.forEach(e -> {
            ExamPaperRelation relation = new ExamPaperRelation();
            relation.setPercentage(proPercentage);
            relation.setPaperId(examPaper.getId());
            relation.setQuestionType(QuestionType.PROGRAMMING_QUESTION);
            relation.setQuestionId(e.getProblemId());
            relations.add(relation);
        });
        objectiveByRandom.forEach(e -> {
            ExamPaperRelation relation = new ExamPaperRelation();
            relation.setPercentage(objPercentage);
            relation.setPaperId(examPaper.getId());
            relation.setQuestionType(QuestionType.OBJECTIVE_QUESTION);
            relation.setQuestionId(e.getId());
            relations.add(relation);
        });
        examPaperRelationService.insertList(relations);
        return examPaper;
    }

    private ExamPaper saveExamPaper(QuestionNumDTO dto) {
        ExamPaper examPaper = new ExamPaper();
        examPaper.setNum(dto.getObjectiveNum() + dto.getProgrammingNum());
        examPaper.setTitle(dto.getTitle());
        examPaper.setTotalScore(dto.getTotalScore());
        User user = UserInfoShareHolder.getUserInfo().getUser();
        Long id = user.getId();
        examPaper.setCreatorId(id);
        examPaper.setCompanyId(UserInfoShareHolder.getUserInfo().getCompanyId());
        examPaper.setCreateTime(new Timestamp(System.currentTimeMillis()));
        examPaper.setDisabled(Boolean.FALSE);
        super.save(examPaper);
        return examPaper;
    }

    private void validGroupParams(QuestionNumDTO dto) {
        Integer objectiveNum = Optional.ofNullable(dto.getObjectiveNum()).orElseThrow(() -> ExceptionUtils.exception("客观题数量不能为空"));
        Integer programmingNum = Optional.ofNullable(dto.getProgrammingNum()).orElseThrow(() -> ExceptionUtils.exception("编程题数量不能为空"));
        if (objectiveNum <= 0) {
            throw ExceptionUtils.exception("客观题数量不合法");
        }
        if (programmingNum <= 0) {
            throw ExceptionUtils.exception("编程题数量不能为空");
        }
        if (StringUtils.isBlank(dto.getTitle())) {
            throw ExceptionUtils.exception("试卷标题不能为空");
        }
        if (dto.getTotalScore() == null) {
            throw ExceptionUtils.exception("试卷总分不能为空");
        }
        Double objPercentage = Optional.ofNullable(dto.getObjectivePercentage()).orElseThrow(() -> ExceptionUtils.exception("客观题占比分数不能为空"));
        Double proPercentage = Optional.ofNullable(dto.getProgrammingPercentage()).orElseThrow(() -> ExceptionUtils.exception("编程题占比分数不能为空"));
        BigDecimal objSum = BigDecimal.valueOf(objPercentage).multiply(BigDecimal.valueOf(objectiveNum));
        BigDecimal proSum = BigDecimal.valueOf(proPercentage).multiply(BigDecimal.valueOf(programmingNum));
        BigDecimal sum = objSum.add(proSum);
        if (BigDecimal.ONE.compareTo(sum) != 0) {
            throw ExceptionUtils.exception("客观题分数占比与编程题分数占比不为1");
        }
        String title = Optional.ofNullable(dto.getTitle()).orElseThrow(() -> ExceptionUtils.exception("试卷标题不能为空"));
        boolean exists = super.exists(CombineClause.and(
                SingleClause.equal("company_id", UserInfoShareHolder.getUserInfo().getCompanyId()),
                SingleClause.equal("title", title)
        ));
        if (exists) {
            throw ExceptionUtils.exception("存在重名试卷名称：%s", title);
        }
    }

    @Override
    public Map<Long, String> getNameMapByIds(List<Long> paperIds) {
        if (CollectionUtils.isEmpty(paperIds)) {
            return Collections.emptyMap();
        }
        return super.selectList(SingleClause.in("id", paperIds.toArray(new Object[0]))).stream()
                .collect(Collectors.toMap(ExamPaper::getId, ExamPaper::getTitle));
    }

    @Override
    public ExamPaper getById(Long id) {
        if (id == null) {
            throw ExceptionUtils.exception("标识不能为空");
        }
        return mapper.selectById(id);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateExamPaper(ExamPaper examPaper) {
        Long id = examPaper.getId();
        if (id == null) {
            throw ExceptionUtils.exception("请求选择试题");
        }
        valid(examPaper);
        mapper.updateById(examPaper);
        List<ObjectiveQuestionDTO> objectiveQuestions = examPaper.getObjectiveQuestions();
        List<ProgrammingQuestionDTO> programmingQuestions = examPaper.getProgrammingQuestions();
        List<ExamPaperRelation> relations = new ArrayList<>();
        if (!CollectionUtils.isEmpty(objectiveQuestions)) {
            objectiveQuestions.forEach(e -> {
                ExamPaperRelation examPaperRelation = new ExamPaperRelation();
                examPaperRelation.setPaperId(id);
                examPaperRelation.setQuestionId(e.getId());
                examPaperRelation.setQuestionType(QuestionType.OBJECTIVE_QUESTION);
                examPaperRelation.setPercentage(e.getPercentage());
                relations.add(examPaperRelation);
            });
        }
        if (!CollectionUtils.isEmpty(programmingQuestions)) {
            programmingQuestions.forEach(e -> {
                ExamPaperRelation examPaperRelation = new ExamPaperRelation();
                examPaperRelation.setPaperId(id);
                examPaperRelation.setQuestionId(e.getProblemId());
                examPaperRelation.setQuestionType(QuestionType.PROGRAMMING_QUESTION);
                examPaperRelation.setPercentage(e.getPercentage());
                relations.add(examPaperRelation);
            });
        }
        examPaperRelationService.deleteByExamPaperId(id);
        examPaperRelationService.insertList(relations);
    }

    @Override
    public ExamPaper getExamPaperWithOnlyProgrammingQuestion(Long paperId) {
        if (paperId == null) {
            throw ExceptionUtils.exception("试卷标识不能为空");
        }
        return this.getExamPaperDetail(paperId, PaperType.ONLY_PROGRAMMING_QUESTIONS);
    }

    @Override
    public Map<Long, Set<String>> getProblemPoints(Long paperId) {
        List<Long> ids = getQuestionIdByProgramming(paperId);
        return mapper.getProblemPoints(ids).stream().collect(Collectors.toMap(ProblemPointsDTO::getProblemId, e -> new HashSet<>(e.getPoints())));
    }

    @Override
    public List<Long> getQuestionIdByProgramming(Long paperId) {
        if (paperId == null) {
            throw ExceptionUtils.exception("试卷标识不能为空");
        }
        return examPaperRelationService.getRelationByPaperId(paperId).stream()
                .filter(e -> e.getQuestionType() == QuestionType.PROGRAMMING_QUESTION).map(ExamPaperRelation::getQuestionId)
                .collect(Collectors.toList());
    }

    @Override
    public List<ExamPaper> getByCreatorId(Long creatorId, Long companyId) {
        if (creatorId == null || companyId == null) {
            return Collections.emptyList();
        }
        return super.selectList(CombineClause.and(
                SingleClause.equal("creator_id", creatorId),
                SingleClause.equal("company_id", companyId)
        ));
    }

    @Override
    public int DeleteExamPaper(Long paperId) {
        if(paperId == null){
            throw ExceptionUtils.exception("试卷标识不能为空");
        }
        UpdateWrapper<ExamPaper> updateWrapper = new UpdateWrapper<ExamPaper>()
                .eq("id", paperId)
                .set("disabled", 1);
        int i = mapper.update(null, updateWrapper);
        return i;
    }

    @Override
    public Map<Long, String> getQuestionNameMap(Long paperId) {
        List<Long> ids = examPaperRelationService.getRelationByPaperId(paperId).stream().map(ExamPaperRelation::getQuestionId).collect(Collectors.toList());
        List<ProblemViewDto> problemByIds = newProblemService.getProblemByIds(ids);
        return problemByIds.stream().collect(Collectors.toMap(ProblemViewDto::getProblemId, ProblemViewDto::getProblemName));
    }

}
